package org.KeyCrafter.server;

import io.netty.handler.codec.redis.RedisMessage;
import org.KeyCrafter.ds.dict.Dict;
import org.KeyCrafter.ds.dict.DictType;
import org.KeyCrafter.ds.dict.HashDict;
import org.KeyCrafter.ds.dict.MurmurHash2;
import org.KeyCrafter.obj.KeyCrafterObject;
import org.KeyCrafter.server.session.ClientSession;
import org.KeyCrafter.util.ReplyUtil;
import org.KeyCrafter.util.SharedReply;

import static org.KeyCrafter.ds.dict.Dict.DICT_OK;
import static org.KeyCrafter.server.ServerConstants.*;

public class DefaultKVDB extends KVDB {
    private final static String NON_EXISTENT_STR = ": does not exist";
    private final static DictType dictType = new MurmurHash2();
    // 数据库键空间，保存着数据库中的所有键值对
    Dict dict;

    // 键的过期时间，字典的键为键，字典的值为过期事件 UNIX 时间戳
    Dict expires;

    // 正处于阻塞状态的键
    Dict blockingKeys;

    // 可以解除阻塞的键
    Dict readyKeys;

    // 正在被 WATCH 命令监视的键
    Dict watchedKeys;

    EvictionPoolEntry evictionPool;

    // 数据库号码
    int id;

    @Override
    public long getAvgTTL() {
        return avgTTL;
    }

    void setAvgTTL(long avgTTL) {
        this.avgTTL = avgTTL;
    }

    // 数据库的键的平均 TTL（ Time To Live），统计信息
    long avgTTL;

    /*
     * 为执行写入操作而取出键 key 在数据库 db 中的值。
     *
     * 和 lookupKeyRead 不同，这个函数不会更新服务器的命中/不命中信息。
     *
     * 找到时返回值对象，没找到返回 NULL 。
     */
    public KeyCrafterObject lookupKeyWrite(String key) {
        // 删除过期键
        expireIfNeeded(key);

        // 查找并返回 key 的值对象
        return lookupKey(key);
    }

    DefaultKVDB(int id) {
        this.dict = new HashDict(dictType,null);
        this.expires = new HashDict(dictType,null);
        this.id = id;
        this.avgTTL = 0;
    }

    /*
     * 从数据库 db 中取出键 key 的值（对象）
     *
     * 如果 key 的值存在，那么返回该值；否则，返回 NULL 。
     */
    private KeyCrafterObject lookupKey(String key) {
        // 查找键空间
        Dict.Entry de = this.dict.find(key);

        // 节点存在
        if (de != null) {
            // 取出值
            KeyCrafterObject val =(KeyCrafterObject) de.getValue();
            // 更新时间信息（只在不存在子进程时执行，防止破坏 copy-on-write 机制）
            //  if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
            //      val->lru = LRU_CLOCK();
            val.setLRU(sc.LRU_CLOCK());
            // 返回值
            return val;
        } else {
            // 节点不存在
            return null;
        }
    }

    /*
     * 检查 key 是否已经过期，如果是的话，将它从数据库中删除。
     *
     * 返回 false 表示键没有过期时间，或者键未过期。
     *
     * 返回 true 表示键已经因为过期而被删除了。
     */
    private boolean expireIfNeeded(String key) {
        // 取出键的过期时间
        long when = this.getExpire(key);

        // 没有过期时间
        if (when < 0) {
            return false;
        }
        // 如果服务器正在进行载入，那么不进行任何过期检查
        // if (server.loading) return 0;
        long now = System.currentTimeMillis();
        // 如果未过期，返回 false
        if (now <= when) {
            return false;
        }

        // 删除过期key
        sc.getStatistics().incrExpiredKeys();
        return delete(key);
    }


    /*
     * 从数据库中删除给定的键，键的值，以及键的过期时间。
     *
     * 删除成功返回 true ，因为键不存在而导致删除失败时，返回 false 。
     */

    public boolean delete(String key) {
        // 删除键的过期时间
        if (this.expires.size() > 0) {
            this.expires.delete(key);
        }
        // 删除键值对
        if (this.dict.delete(key) == DICT_OK) {
            return true;
        } else {
            // 键不存在
            return false;
        }
    }

    /*
     * 将键 key 的过期时间设为 when
     */
    @Override
    public void setExpire(String key, long when) {
        Dict.Entry kde,de;
        // 取出键
        kde = this.dict.find(key);
        assert kde != null : key + NON_EXISTENT_STR;
        // 根据键取出键的过期时间
        de = this.expires.replaceRaw(kde.getKey());
        // 设置键的过期时间
        // 这里是直接使用整数值来保存过期时间，不是用 INT 编码的 String 对象
        de.setValue(when);
    }

    @Override
    public void incrDirty() {
        sc.incrDirty();
    }

    @Override
    public void addDirty(int delta) {
        sc.addDirty(delta);
    }

    /*
     * 返回给定 key 的过期时间。
     *
     * 如果键没有设置过期时间，那么返回 -1 。
     */
    public long getExpire(String key) {
        Dict.Entry de = null;
        // 获取键的过期时间
        // 如果过期时间不存在，那么直接返回
        if (this.expires.size() == 0 ||
                (de = this.expires.find(key)) == null) {
            return -1;
        }
        assert this.dict.find(key) != null : key + NON_EXISTENT_STR;
        // 返回过期时间
        return (long)de.getValue();
    }

    /*
     * 高层次的 SET 操作函数。
     *
     * 这个函数可以在不管键 key 是否存在的情况下，将它和 val 关联起来。
     *
     * 1) The ref count of the value object is incremented.
     *    值对象的引用计数会被增加
     *
     * 2) clients WATCHing for the destination key notified.
     *    监视键 key 的客户端会收到键已经被修改的通知
     *
     * 3) The expire time of the key is reset (the key is made persistent).
     *    键的过期时间会被移除（键变为持久的）
     */
    public void setKey(String key, KeyCrafterObject val) {
        // 添加或覆写数据库中的键值对
        if (lookupKeyWrite(key) == null) {
            dbAdd(key,val);
        } else {
            dbOverwrite(key,val);
        }
    }

    /* Overwrite an existing key with a new value. Incrementing the reference
     * 为已存在的键关联一个新值。
     *
     * 这个函数不会修改键的过期时间。
     * 如果键不存在，那么函数停止。
     */

    private void dbOverwrite(String key, KeyCrafterObject val) {
        Dict.Entry de = this.dict.find(key);

        // 节点必须存在，否则中止
        assert de != null : key + "does not exist";

        // 覆写旧值
        this.dict.replace(key,val);
    }

    /*
     * 尝试将键值对 key 和 val 添加到数据库中。
     *
     * 程序在键已经存在时会停止。
     */
    @Override
    public void dbAdd(String key, KeyCrafterObject val) {

        int retval =this.dict.add(key,val);
        // 如果键已经存在，那么停止
        assert retval == KeyCrafter_OK : key.toString() + " is null";
    }



    /*
     * 为执行读取操作而从数据库中查找返回 key 的值。
     *
     * 如果 key 存在，那么返回 key 的值对象。
     *
     * 如果 key 不存在，那么向客户端发送 reply 参数中的信息，并返回 NULL 。
     */

    public KeyCrafterObject lookupKeyReadOrReply(ClientSession c, String key, RedisMessage reply) {

        // 查找
        KeyCrafterObject o = lookupKeyRead(key);

        // 决定是否发送信息
        if (o == null)
            ReplyUtil.addReply(c,reply);

        return o;
    }

    /*
     * 为执行读取操作而取出键 key 在数据库 db 中的值。
     *
     * 并根据是否成功找到值，更新服务器的命中/不命中信息。
     *
     * 找到时返回值对象，没找到返回 NULL 。
     */
    @Override
    public KeyCrafterObject lookupKeyRead(String key) {
        KeyCrafterObject val;

        // 检查 key 释放已经过期
        expireIfNeeded(key);

        // 从数据库中取出键的值
        val = lookupKey(key);

        // 更新命中/不命中信息
        if (val == null) {
            sc.getStatistics().incrKeyspaceMisses();
        }
        else {
            sc.getStatistics().incrKeyspaceHits();
        }
        // 返回值
        return val;
    }


    @Override
    public int id() {
        return this.id;
    }

    @Override
    Dict getKeySpace() {
        return this.dict;
    }

    @Override
    Dict getExpires() {
        return this.expires;
    }

    @Override
    public void delCommand(ClientSession c) {
        int deleted = 0;
        int argc = c.argc();
        // 遍历所有输入键
        for (int j = 1; j < argc; j++) {
            String key = c.argv(j);
            // 先删除过期的键
            expireIfNeeded(key);
            // 尝试删除键
            if (delete(key)) {
                sc.incrDirty();
                // 成功删除才增加 deleted 计数器的值
                deleted++;
            }
        }
        // 返回被删除键的数量
        ReplyUtil.addReplyLong(c,deleted);
    }

    @Override
    public void selectCommand(ClientSession c) {
        int id = 0;
        try {
            id = Integer.parseInt(c.argv(1));
        }catch (NumberFormatException e) {
            ReplyUtil.addReplyError(c,"invalid DB index");
        }
        // 切换数据库
        if (selectDb(c,id) != KeyCrafter_OK) {
            ReplyUtil.addReplyError(c,"invalid DB index");
        } else {
            ReplyUtil.addReply(c, SharedReply.ok);
        }
    }
}
